#====  PACKAGE =================================================================
#         NAME:  Network
#      PURPOSE:  Container class for nodes.
#   PARAMETERS:  name(String), nodelist(Hash)
#      RETURNS:  True
#===============================================================================

#- Overview --------------------------------------------------------------------
# get_name		is_network		rewind
# get_random_node	set_name		export
# get_node		create_node		print
# get_node_keys		get_next		create_edge
# get_by_state		
#-------------------------------------------------------------------------------

package insj_m_network;
use insj_m_node;
use insj_m_parser;
use insj_m_infection;
use strict;
use warnings;
use Carp;
use Scalar::Util qw(reftype);
#use Math::Random::MT::Perl qw(rand);
@Network::ISA = qw();

#===  FUNCTION  ================================================================
#         NAME:  new
#      PURPOSE:  Constructor
#   PARAMETERS: 
#      RETURNS:  Network Object
#===============================================================================
sub new {

my ($class) = shift @_;
    my (%arg)   = @_;
    my %entry;
    if ( defined $arg{entry} ) {
        %entry = %{ $arg{entry} };
    }
    else {
        %entry = %arg;
    }
    my %nodelist;
    bless {
        _name     => $entry{name},
	_nodelist => \%nodelist
        }, $class;
}

sub DESTROY {
	$_[0]->rewind;
	while (my $node = $_[0]->get_next) {
		$node -> delete_nodelist;
		}
	$_[0]->delete_nodelist;
	}

sub delete_nodelist {
	delete $_[0]->{_nodelist};
	}

#---------------------------------------------------------------------------
# getters
#---------------------------------------------------------------------------
sub get_name	{ $_[0]->{_name} }
sub get_random_node { 
		my @keys =  keys %{ $_[0]->{_nodelist}};
	        my $node =  ${ $_[0]->{_nodelist}}{ $keys[rand @keys] };
		return $node;
		}
sub get_node      { return ${ $_[0]->{_nodelist}}{ $_[1] }; }
sub get_node_keys { return keys %{ $_[0]->{_nodelist}}; }

#---------------------------------------------------------------------------
# identifier
#---------------------------------------------------------------------------
sub is_network	{ 1 }

#---------------------------------------------------------------------------
# setters
#---------------------------------------------------------------------------
sub set_name       { $_[0]->{_name}       = $_[1] }

#---------------------------------------------------------------------------
# functions
#---------------------------------------------------------------------------

#===  FUNCTION  ================================================================
#         NAME:  create_node
#      PURPOSE:  Creates and adds a new node to nodelist. Return this node.
#   PARAMETERS:  name(String)
#      RETURNS:  Node object
#        USAGE:  my $node = $network->create_node(name => "1");
#===============================================================================
sub create_node {
	my $self = shift @_;
	my %arg  = @_;
   	my %entry;
   	if ( defined $arg{entry} ) {
        	%entry = %{ $arg{entry} };
   	}
    	else {
       		%entry = %arg;
		}
	my $new_node = insj_m_node->new( name => $entry{name} );
	
	$self->{_nodelist}{ $new_node->get_name() } = $new_node;
	
	return $new_node;
}

#===  FUNCTION  ================================================================
#         NAME:  create_edge
#      PURPOSE:  Creates a uni or bi directional edge.
#   PARAMETERS:  NodeA(Str), NodeB(Str), String("bi","uni")
#      RETURNS:  True
#        USAGE:  $network -> create_edge
#===============================================================================
sub create_edge {
	my $self = shift @_;
	my $nodeA; my $nodeB;

	if ( ref($_[0]) eq "HASH") { 
		if ($_[0]->is_node == 1){ $nodeA = $_[0] }
		} 
	else { $nodeA = $self->get_node($_[0]) }
	
	if ( ref($_[1]) eq "HASH") { 
		if ($_[1]->is_node == 1){ $nodeB = $_[1] } 
		}
	else { $nodeB = $self->get_node($_[1]) }

	if ($_[2] eq "bi"){
		$nodeA->add_node($nodeB);
		$nodeB->add_node($nodeA);
		}

	if ($_[2] eq "uni"){
		$nodeA->add_node($nodeB);
		}
	
	return 1;
}

#===  FUNCTION  ================================================================
#         NAME:  remove_edge
#      PURPOSE:  Removes a uni or bi directional edge.
#   PARAMETERS:  NodeA(Str or Obj), NodeB(Str or Obj), String("bi","uni")
#      RETURNS:  True
#        USAGE:  $network -> remove_edge
#===============================================================================
sub remove_edge {
	my $self = shift @_;
	my $nodeA; my $nodeB;

	if (defined $_[0]->is_node) { $nodeA = $_[0] } else { $nodeA = $self->get_node($_[0]) }
	
	if (defined $_[1]->is_node) { $nodeB = $_[1] } else { $nodeB = $self->get_node($_[1]) }

	if ($_[2] eq "bi"){
		$nodeA->remove_edge($nodeB->get_name);
		$nodeB->remove_edge($nodeA->get_name);
		}

	if ($_[2] eq "uni"){
		$nodeA->remove_edge($nodeB->get_name);
		}
	
	return 1;
}


#===  FUNCTION  ================================================================
#         NAME:  get_next
#      PURPOSE:  iterator for nodelist
#   PARAMETERS:  None
#      RETURNS:  Node object
#===============================================================================
sub get_next {
    my $key;

    $key = each %{ $_[0]->{_nodelist} };
    if ( defined $key ) {
        my $next = $_[0]->{_nodelist}->{$key};
        return $next;
    }
    return undef;
}

#===  FUNCTION  ================================================================
#         NAME:  get_by_state
#      PURPOSE:  get array of node names by state
#   PARAMETERS:  state(Sca, Sca)
#      RETURNS:  node name array
#===============================================================================
sub get_by_state {
	$_[0]->rewind;
	my @list;
	while(my $node = $_[0]->get_next){
		if ($node->get_state($_[1]) eq $_[2]) {
			push(@list, $node->get_name);
			}
		}
	return @list;	
	}

#===  FUNCTION  ================================================================
#         NAME:  rewind
#      PURPOSE:  rewind resets the iterator for nodelist
#   PARAMETERS:  none
#      RETURNS:  true
#===============================================================================
sub rewind {
    keys %{ $_[0]->{_nodelist} };
    1;
}

#===  FUNCTION  ================================================================
#         NAME:  print
#      PURPOSE:  test output of the network human readable
#   PARAMETERS:  none
#      RETURNS:  none
#===============================================================================
sub print {
	my $network = shift @_;
	print 'Network: '.$network->get_name."\n";
	$network -> rewind;
	while(my $node = $network -> get_next ) {
		if ( $node->get_name =~ /a/) { next; }
		print "\tNode (".$node->get_name.") ";

		my %hash = $node->get_state_hash;
		
		while ( (my $key, my $value) = each %hash ){
  				print "$key ($hash{$key}) ";}

		print "\tEdges ( ";

		$node -> rewind;
		while(my $edge = $node -> get_next ) {
			print $edge -> get_name." ";		
			}
		print ") \n";
		# Print infections if present.
		while (my $infection = $node -> get_next('infection')) {
			print "\t Infection ".$infection->get_name." \n";
			}
		}
	}

#===  FUNCTION  ================================================================
#         NAME:  export
#      PURPOSE:  calls export on insj_m_parser.pm
#   PARAMETERS:  path + name (string), format (String)
#      RETURNS:  true
#===============================================================================
sub export {
	my $self   = $_[0];
	my $path   = $_[1];
	my $format = $_[2];
	
	$self->rewind;
	insj::Parser::export($path, $format, $self);

	1;
	}

1;
